using System;
using System.Net;
using System.ComponentModel;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SoftwareMonkeys.Scriptech.Diagnostics;

namespace SoftwareMonkeys.Scriptech.Ftp
{
	public class FtpUploader : Component
	{
		FtpWebRequest ftpRequest = null;
		
		List<string> outputMessages = new List<string>();
		public string[] OutputMessages
		{
			get { return outputMessages.ToArray(); }
		}
		
		string server = string.Empty;
		string username = string.Empty;
		string password = string.Empty;
		
		public string CurrentDirectory = String.Empty;
		
		public FtpUploader (string server, string username, string password)
		{
			using (LogGroup logGroup = LogGroup.StartDebug("Constructing FTP uploader."))
			{
				this.server = server;
				this.username = username;
				this.password = password;
				
				if (server.ToLower().IndexOf("ftp://") == -1)
					this.server = "ftp://" + server;
				
				LogWriter.Debug("Server: " + server);
				LogWriter.Debug("Username: " + username);
			}
		}
		
		public string Upload(string localFilePath, string remoteFilePath)
		{
			return Upload(localFilePath, remoteFilePath, false);
		}
		
		public string Upload(string localFilePath, string remoteFilePath, bool overwrite)
		{
			string fullRemoteFilePath = String.Empty;
			using (LogGroup logGroup = LogGroup.StartDebug("Uploading file via FTP: " + localFilePath))
			{
				LogWriter.Debug("Local file path: " + localFilePath);
				LogWriter.Debug("Remote file path: " + remoteFilePath);
				
				LogWriter.Debug("Overwrite: " + overwrite.ToString());
				
				fullRemoteFilePath = GetRemotePath(server, CurrentDirectory, remoteFilePath);
				
				LogWriter.Debug("Full remote file path: " + fullRemoteFilePath);
				
				//string remoteDir = fullRemoteFilePath.Replace(Path.GetFileName(remoteFilePath), "");
				
				string fullRemoteDirectoryPath  = GetRemotePath(server, CurrentDirectory, Path.GetDirectoryName(remoteFilePath));
				
				LogWriter.Debug("Full remote directory path: " + fullRemoteDirectoryPath);
				
				LogWriter.Debug("Full remote file path: " + fullRemoteFilePath);
				
				if (!DirectoryExists(fullRemoteDirectoryPath))
					CreateDirectory(fullRemoteDirectoryPath);
				
				bool alreadyExists = FileExists(fullRemoteFilePath);
				
				LogWriter.Debug("Already exists: " + alreadyExists.ToString());
				
				if (!alreadyExists || overwrite)
				{
					if (!alreadyExists)
						LogWriter.Debug("New file. Uploading.");
					else
						LogWriter.Debug("Existing file. Overwriting.");
					
					// Get the object used to communicate with the server.
					ftpRequest = (FtpWebRequest)WebRequest.Create(fullRemoteFilePath);
					ftpRequest.Method = WebRequestMethods.Ftp.UploadFile;
					
					// This example assumes the FTP site uses anonymous logon.
					ftpRequest.Credentials = new NetworkCredential (username,password);
					
					// Copy the contents of the file to the request stream.
					//StreamReader sourceStream = new StreamReader(localFilePath);
					//byte [] fileContents = Encoding.Unicode.GetBytes(sourceStream.ReadToEnd());
					//sourceStream.Close();
					//ftpRequest.ContentLength = fileContents.Length;
					
					//Stream requestStream = ftpRequest.GetRequestStream();
					//requestStream.Write(fileContents, 0, fileContents.Length);
					//requestStream.Close();
					
					
					FileStream fs = File.OpenRead(localFilePath);
					byte[] buffer = new byte[fs.Length];
					fs.Read(buffer, 0, buffer.Length);
					fs.Close();
					Stream ftpstream = ftpRequest.GetRequestStream();
					ftpstream.Write(buffer, 0, buffer.Length);
					ftpstream.Close();
					
					try
					{
						FtpWebResponse response = (FtpWebResponse)ftpRequest.GetResponse();
					
						response.Close();
					
						outputMessages.Add("FTP) Uploaded file: " + fullRemoteFilePath);
					}
					catch (Exception ex)
					{
						throw new Exception("Failed to upload: '" + fullRemoteFilePath, ex);
					}
					
					LogWriter.Debug("File uploaded.");
				}
				else
					LogWriter.Debug("File exists. Skipping.");
			}
			return fullRemoteFilePath;
		}
		
		public void CreateDirectory(string directory)
		{
			using (LogGroup logGroup = LogGroup.StartDebug("Creating a directory via FTP."))
			{
				LogWriter.Debug("Directory: " + directory);
				
				string parentDirectoryPath = "/";
				if (directory.IndexOf("/") >- 1)
					parentDirectoryPath = directory.Substring(0, directory.LastIndexOf("/"));
				
				LogWriter.Debug("Parent directory: " + parentDirectoryPath);
				
				//string parentDirectoryName = Path.GetFileName(parentDirectoryPath.TrimEnd(Path.DirectorySeparatorChar));
				
				//if (parentDirectoryName != String.Empty)
				//	parentDirectoryPath = parentDirectoryPath.Replace(parentDirectoryName, "").TrimEnd('/');
				
				string fullDirectory = GetRemotePath(server, CurrentDirectory, directory);
				
				LogWriter.Debug("Full directory: " + fullDirectory);
				
				if (!DirectoryExists(parentDirectoryPath))
					CreateDirectory(parentDirectoryPath);
				
				FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(fullDirectory.TrimEnd('/'));
				
				request.Method = WebRequestMethods.Ftp.MakeDirectory;
				request.Credentials = new NetworkCredential(username, password);
				try
				{
					using (var resp = (FtpWebResponse) request.GetResponse())
					{
						outputMessages.Add("FTP) Created directory: " + fullDirectory);
					}
					
					LogWriter.Debug("Created directory");
				}
				catch (Exception ex)
				{
					throw new Exception("Failed to create FTP directory: " + fullDirectory, ex);
				}
			}
		}
		
		public bool DirectoryExists(string directory)
		{
			bool exists = false;
			using (LogGroup logGroup = LogGroup.StartDebug("Checking whether the specified directory exists."))
			{
				LogWriter.Debug("Directory: " + directory);
				
				string fullDirectory = GetRemotePath(server, CurrentDirectory, directory.Trim(Path.DirectorySeparatorChar));
				
				LogWriter.Debug("Full directory: " + fullDirectory);
				
				FtpWebRequest request = null;

				try
				{
					request = (FtpWebRequest)FtpWebRequest.Create(fullDirectory.TrimEnd('/'));
				}
				catch (UriFormatException ex)
				{
					throw new Exception("Invalid directory: " + fullDirectory, ex);
					
					//outputMessages.Add("Error: Invalid directory: " + fullDirectory + "\n" + ex.ToString());
				}
				
				if (request != null)
				{
					request.Credentials = new NetworkCredential(username, password);
					request.Method = WebRequestMethods.Ftp.GetDateTimestamp;
					request.KeepAlive = false;
					
					try
					{
						FtpWebResponse response = (FtpWebResponse)request.GetResponse();
						
						// If no exception occurred then the directory exists
						exists = true;
					}
					catch (WebException ex)
					{
						FtpWebResponse response = (FtpWebResponse)ex.Response;
						if (response.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable)
							exists = false;
						else if (ex.Message.IndexOf("(550) File unavailable") > -1)
							exists = false;
						else
							throw new Exception("Failed to check directory: " + fullDirectory, ex);
					}
				}
				
				LogWriter.Debug("Exists: " + exists.ToString());
			}
			return exists;
		}
		
		public bool FileExists(string file)
		{
			bool doesExist = false;
			
			using (LogGroup logGroup = LogGroup.StartDebug("Checking whether the file exists: " + file))
			{
				string fullPath = GetRemotePath(server, CurrentDirectory, file);
				
				LogWriter.Debug("Full path: " + fullPath);
				
				try {
					var request = (FtpWebRequest)WebRequest.Create(fullPath);
					request.Credentials = new NetworkCredential(username, password);
					request.Method = WebRequestMethods.Ftp.GetDateTimestamp;
					request.KeepAlive = false;
					
					FtpWebResponse response = (FtpWebResponse)request.GetResponse();
					
					// If no error occured then the file must exist
					doesExist = true;
				}
				catch (WebException ex)
				{
					FtpWebResponse response = (FtpWebResponse)ex.Response;
					if (response.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable)
						doesExist = false;
					else
						doesExist = true;
				}
				
				LogWriter.Debug("Does exist: " + doesExist);
			}

			// If no exception occurred then the file exists
			return doesExist;
		}
		public bool IsNewer(string file, string remoteFile) {
			
			DateTime fileDate = GetFileDate(file);
			DateTime remoteFileDate = GetRemoteDate(remoteFile);
			
			return fileDate > remoteFileDate;

		}
		
		public DateTime GetFileDate(string file)
		{
			return File.GetLastWriteTime(file);
		}
		
		public DateTime GetRemoteDate(string remoteFile)
		{
			DateTime remoteFileDate = DateTime.MinValue;
			
			using (LogGroup logGroup = LogGroup.StartDebug("Retrieving the last modified date of the remote file: " + remoteFile))
			{
				if (FileExists(remoteFile))
				{
					string fullRemoteFile = GetRemotePath(server, CurrentDirectory, remoteFile);
					
					LogWriter.Debug("Full remote file: " + fullRemoteFile);
					
					try {
						FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(fullRemoteFile);
						
						request.Credentials = new NetworkCredential(username, password);
						request.Method = WebRequestMethods.Ftp.GetDateTimestamp;
						request.KeepAlive = false;
						
						FtpWebResponse response = (FtpWebResponse)request.GetResponse();
						
						using( StreamReader sr = new StreamReader( response.GetResponseStream() ) )
						{
							remoteFileDate = response.LastModified;
						}
						
						LogWriter.Debug("Remote file date: " + remoteFileDate);

					}
					catch (WebException ex)
					{
						throw new Exception("Failed to get remote date of file '" + fullRemoteFile + "'.", ex);
					}
				}
				else
					LogWriter.Debug("Remote file not found.");
			}
			return remoteFileDate;
		}
		
		public string GetRemotePath(string server, string currentDirectory,  string remoteFile)
		{
			string remotePath = String.Empty;
			using (LogGroup logGroup = LogGroup.StartDebug("Creating the full remote path to '" + remoteFile + "'."))
			{
				LogWriter.Debug("Server: " + server);
				LogWriter.Debug("Current directory: " + currentDirectory);
				LogWriter.Debug("Remote file: " + remoteFile);
				
				if (remoteFile.IndexOf("ftp:") == -1)
					remotePath = server.TrimEnd('/') + "/" + currentDirectory.Trim('/') + "/" + remoteFile.Replace(@"\", "/").Trim('/');
				else
					remotePath = remoteFile;
				
				remotePath = remotePath.TrimEnd('/');
				
				LogWriter.Debug("Remote path: " + remotePath);
			}
			
			return remotePath;
		}
		
		protected override void Dispose (bool release_all)
		{
			if (release_all)
			{
				if (ftpRequest != null)
				{
					ftpRequest.Abort();
					ftpRequest = null;
				}
			}
			
			base.Dispose (release_all);
		}
		
		public void ClearMessages()
		{
			outputMessages.Clear();
		}
	}
}

